package model.algorithms;

import java.util.List;

import model.framework.Block;
import model.framework.Field;

public class LowestFit extends Algorithm {

	/**
	 * 
	 */
	public void run( List<Block> blocks, Field field ) {
		/* Lowest found value */
		int lowest;
		
		/*
		 * The lowest found position has three values:
		 * 0: X position
		 * 1: Y position
		 * 2: rotation
		 */
		int[] lowestPosition;
		
		/* Every block */
		for( Block block : blocks ) {

			/* Reset the lowest position */
			lowestPosition = new int[3];

			/* Get the latest field surface */
			int[] fieldSurface = field.getSurface();

			/* Try to find a placing lower than this */
			lowest = Integer.MAX_VALUE;

			/* Try every rotation */
			for( int rotation = 0; rotation < block.getRotations(); rotation ++ ) {

				/* Loop through the surface heights */
				for( int indexX = 0; indexX < ( fieldSurface.length - block.getBlockType().getWidth( rotation ) ) + 1; indexX ++ ) {

					/* Find the relevant Field data: the delta */
					int[] fieldDelta = calculateDelta( indexX, ( indexX + block.getBlockType().getWidth( rotation ) - 1 ), fieldSurface );
					
					/* Get the block surface */
					int[] blockSurface = block.getSurface( rotation );
					
					/* Find the lowest place */
					int currentHeight = calculateHeightAfterPlacing( blockSurface, fieldSurface, block.getBlockType().getHeight( rotation ), indexX );
					
					/* If it's lower than the lowest we've found already, set it as the new lowest */
					if( currentHeight < lowest ) {
						
						/* We've reached a new low */
						lowest = currentHeight;
						
						int[] lowestYPositions = calculateYPositions( blockSurface, fieldDelta, block.getBlockType().getHeight( rotation ), currentHeight );
						
						lowestPosition[0] = indexX;
						lowestPosition[1] = calculateLowestValueInArray( lowestYPositions );
						lowestPosition[2] = rotation;
					}
				}
			}

			block.setX( lowestPosition[0] );
			block.setY( lowestPosition[1] );
			block.setRotation( lowestPosition[2] );
			field.placeBlock( block );
		}
	}
	
	/**
	 * 
	 * @param array
	 * @return
	 */
	private int calculateLowestValueInArray( int[] array ) {
		int lowest = Integer.MAX_VALUE;
		
		for( int index = 0; index < array.length; index ++ ) {
			if( array[ index ] < lowest ) {
				lowest = array[ index ];
			}
		}
		
		return lowest;
	}
	
	/**
	 * 
	 * @param blockSurface
	 * @param fieldDelta
	 * @param blockHeight
	 * @param totalHeight
	 * @return
	 */
	private int[] calculateYPositions( int[] blockSurface, int[] fieldDelta, int blockHeight, int totalHeight ) {
		int[] yPositions = new int[ blockSurface.length ];
		
		for( int index = 0; index < blockSurface.length; index ++ ) {
			yPositions[ index ] = totalHeight - ( blockHeight - blockSurface[ index ] );
		}
		
		return yPositions;
	}
	
	/**
	 * 
	 * @param blockSurface
	 * @param fieldSurface
	 * @param blockHeight
	 * @param startIndex
	 * @return
	 */
	private int calculateHeightAfterPlacing( int[] blockSurface, int[] fieldSurface, int blockHeight, int startIndex ) {
		int height = -1;
		for( int index = 0; index < blockSurface.length; index ++ ) {
			int currentHeight = fieldSurface[ startIndex + index ] + blockHeight - blockSurface[ index ] ;
			if( currentHeight > height ) {
				height = currentHeight;
			}
		}
		return height;
	}

	/**
	 * 
	 * @param start
	 * @param stop
	 * @param surface
	 * @return
	 */
	private int[] calculateDelta( int start, int stop, int[] surface ) {
		int[] delta = new int[( stop - start ) + 1];
		int deltaPosition = 0;

		for( int index = start; index <= stop; index ++ ) {
			delta[deltaPosition] = surface[index];
			deltaPosition ++ ;
		}

		int lowest = calculateLowestSurface( delta );

		deltaPosition = 0;

		for( int index = start; index <= stop; index ++ ) {
			delta[deltaPosition] = surface[index] - lowest;
			deltaPosition ++ ;
		}

		return delta;
	}
	
	/**
	 * @param delta
	 * @param blockSurface
	 * @return
	 */
	private boolean blockFitsExact( int[] delta, int[] blockSurface ) {
		for( int index = 0; index < delta.length; index ++ ) {
			if( delta[index] != blockSurface[index] ) {
				return false;
			}
		}
		return true;
	}
}
